#! /usr/bin/env python

from __future__ import print_function

import sys
from os.path import join, dirname, abspath
from os import remove
import tempfile

pathname = abspath(sys.argv[0])
project_root = dirname(dirname(pathname))
sys.path = [project_root] + sys.path
import ascii_phonons

if sys.version_info.major > 2:
    import tkinter as tk
    import tkinter.filedialog as filedialog
    import configparser
else:
    import Tkinter as tk
    import tkFileDialog as filedialog
    import ConfigParser as configparser

from PIL import ImageTk
from json import dumps

preview_placeholder = join(ascii_phonons.ascii_phonons_path,
                           'images', 'preview.png')

class Application(tk.Frame):

    def __init__(self, master):
        self.button_defaults={'fill': "x", 'padx': 10, 'pady': 10}

        # Setup files and paths
        self.input_file = ''
        self.output_file = tk.StringVar(value='phonon')
        _, self.preview_file = tempfile.mkstemp(dir=tempfile.gettempdir())
        _, self.tmp_conf_file = tempfile.mkstemp(dir=tempfile.gettempdir())

        self.initialise_conf()

        tk.Frame.__init__(self, master)


        self.message = tk.StringVar(value='Messages')
        MessageRow = tk.Frame(self).pack(side="bottom")
        tk.Label(MessageRow, textvariable=self.message, height=1).pack(side="bottom", expand="yes", fill=tk.X)


        self.LeftRightFrames = tk.Frame(self)
        self.LeftFrame = tk.Frame(self.LeftRightFrames)
        self.RightFrame = tk.Frame(self.LeftRightFrames)

        self.add_menu(root)

        self.add_fileopen_row()

        self.add_cell_settings()

        self.add_appearance_row()

        self.add_frame_row()

        self.add_montage_row()

        self.add_render_row()

        self.add_preview_panel()

        self.LeftRightFrames.pack(side="top", expand="yes", fill="x")
        self.LeftFrame.pack(side="left", expand="yes", fill="x")
        self.RightFrame.pack(side="right")

        self.pack(side=tk.TOP, expand="yes", fill="both")

    def add_menu(self, root):
        menubar = tk.Menu(self)
        filemenu = tk.Menu(menubar, tearoff=0)
        filemenu.add_command(label="Read config", command=self.find_and_read_conf)
        filemenu.add_command(label="Write config", command=self.find_and_write_conf)
        filemenu.add_command(label="Reset config", command=self.initialise_conf)
        menubar.add_cascade(label="File", menu=filemenu)
        root.config(menu=menubar)

    def add_fileopen_row(self):
        self.mode = tk.IntVar(value=0)
        FileopenRow = tk.Frame(self.LeftFrame)
        tk.Button(FileopenRow, text='Open ASCII file',
                  command=self.askinputfilename).pack(side="left", **self.button_defaults)
        tk.Entry(FileopenRow, textvariable=self.mode, width=2).pack(side="right")
        tk.Label(FileopenRow, text='Phonon mode index:').pack(side="right")
        FileopenRow.pack(side="top")

    def add_cell_settings(self):

        self.supercell_X = tk.IntVar(value=2)
        self.supercell_Y = tk.IntVar(value=2)
        self.supercell_Z = tk.IntVar(value=2)
        self.miller_a = tk.DoubleVar(value=0)
        self.miller_b = tk.DoubleVar(value=1)
        self.miller_c = tk.DoubleVar(value=0)

        self.unitcell_X = tk.DoubleVar(value=0.0)
        self.unitcell_Y = tk.DoubleVar(value=0.0)
        self.unitcell_Z = tk.DoubleVar(value=0.0)
        self.show_box = tk.BooleanVar(value=True)

        SupercellRow = tk.Frame(self.LeftFrame)
        tk.Label(SupercellRow, text='Supercell', width=24, justify="right").pack(side="left")
        for field in self.supercell_X, self.supercell_Y, self.supercell_Z:
            tk.Entry(SupercellRow, textvariable=field, width=3).pack(side="left")

        for field in self.miller_c, self.miller_b, self.miller_a:
            tk.Entry(SupercellRow, textvariable=field, width=3).pack(side="right")
        tk.Label(SupercellRow, text="Miller indices:").pack(side="right")
        SupercellRow.pack(side="top", expand="yes", fill="x")

        UnitcellRow = tk.Frame(self.LeftFrame)
        tk.Label(UnitcellRow, text='Bounding box location', width=24, justify="right").pack(side="left")
        for field in self.unitcell_X, self.unitcell_Y, self.unitcell_Z:
            tk.Entry(UnitcellRow, textvariable=field, width=3).pack(side="left")
        tk.Checkbutton(UnitcellRow, text="Show box", variable=self.show_box).pack(side="right")
        UnitcellRow.pack(side="top", expand="yes", fill="x")


    def add_appearance_row(self, padding=20):
        self.arrows = tk.BooleanVar(value=False)
        self.normarrows = tk.BooleanVar(value=False)
        self.arrowsize = tk.DoubleVar(value=1.0)
        self.orthographic = tk.BooleanVar(value=False)
        self.atomsize =  tk.DoubleVar(value=0.6)
        self.vibsize =  tk.DoubleVar(value=1.0)
        self.camera_rot = tk.DoubleVar(value=360)
        self.zoom = tk.DoubleVar(value=1.)

        Appearance = tk.Frame(self.LeftFrame, borderwidth=3, relief="groove")
        tk.Label(Appearance, text="Appearance settings").pack(side="top", fill="x")

        AppearanceRow0 = tk.Frame(Appearance)
        tk.Checkbutton(AppearanceRow0, text="Show arrows", variable=self.arrows).pack(side="left")
        tk.Checkbutton(AppearanceRow0, text="Normalise arrows", variable=self.normarrows).pack(side="right")
        AppearanceRow0.pack(side="top", fill="x", expand="yes")

        AppearanceRow1 = tk.Frame(Appearance)
        tk.Entry(AppearanceRow1, textvariable=self.camera_rot, width=4).pack(side="right")
        tk.Label(AppearanceRow1, text="Rotate:").pack(side="right")
        tk.Entry(AppearanceRow1, textvariable=self.zoom, width=3).pack(side="right")
        tk.Label(AppearanceRow1, text="Zoom:").pack(side="right")
        tk.Checkbutton(AppearanceRow1, text="Orthographic", variable=self.orthographic).pack(side="left")
        AppearanceRow1.pack(side="top", fill="x", expand="yes")

        AppearanceScales = tk.Frame(Appearance)
        for label, param in (('Arrow size:', self.arrowsize),
                                      ('Vib size:', self.vibsize),
                                      ('Atom scale:', self.atomsize)):
            tk.Label(AppearanceScales, text=label).pack(side="left")
            tk.Entry(AppearanceScales, textvariable=param, width=3).pack(side="left")
        AppearanceScales.pack(side="top")

        Appearance.pack(side="top", padx=padding, pady=padding)

    def add_montage_row(self):
        self.montage = tk.BooleanVar(value=False)
        tk.Checkbutton(self.LeftFrame, text="Show all modes as montage",
                           variable=self.montage).pack(side="top")

    def add_frame_row(self):
        self.start_frame = tk.IntVar(value=0)
        self.end_frame = tk.IntVar(value=29)
        self.n_frames = tk.IntVar(value=30)
        self.gif = tk.BooleanVar(value=True)

        FrameRow = tk.Frame(self.LeftFrame)
        tk.Label(FrameRow, text='Frame range').pack(side="left")
        tk.Entry(FrameRow, textvariable=self.start_frame, width=3).pack(side="left")
        tk.Entry(FrameRow, textvariable=self.end_frame, width=3).pack(side="left")
        tk.Label(FrameRow, text='Frames/cycle').pack(side="left")
        tk.Entry(FrameRow, textvariable=self.n_frames, width=3).pack(side="left")
        tk.Checkbutton(FrameRow, text="Make .gif", variable=self.gif).pack(side="left")
        tk.Button(FrameRow, text='Launch Blender', command=self.launch_blender).pack(side="right", **self.button_defaults)
        FrameRow.pack(side="top", expand="yes", fill="x")

    def add_render_row(self):
        self.RenderRow = tk.Frame(self.LeftFrame)
        tk.Button(self.RenderRow, text='Output file', command=self.askoutputfilename).pack(side="left", **self.button_defaults)
        self.output_file_entry = tk.Entry(self.RenderRow, textvariable=self.output_file).pack(side="left", expand="yes", fill="x")
        tk.Button(self.RenderRow, text='Render', command=self.render).pack(side="right", **self.button_defaults)
        tk.Button(self.RenderRow, text='Preview', command=self.preview).pack(side="right", **self.button_defaults)            
        self.RenderRow.pack(side=tk.TOP, expand="yes", fill="x")

    def add_preview_panel(self):
        # Setup preview panel
        self.preview_label = tk.Label(self.RightFrame)
        self.preview_placeholder_pil = ImageTk.Image.open(preview_placeholder)
        self.preview_placeholder_tk = ImageTk.PhotoImage(self.preview_placeholder_pil)
        self.preview_label.configure(image=self.preview_placeholder_tk)
        self.preview_label.pack(padx=10)


    def askinputfilename(self):
        """Set the input .ASCII file with a native system dialogue"""
        self.input_file = filedialog.askopenfilename(defaultextension='.ascii',
                                                     filetypes=(('All files', '*'),
                                                                ('v_sim','*.ascii')))

    def askoutputfilename(self):
        """Set the output file path and root with a native system dialogue"""
        output_file = filedialog.asksaveasfilename()
        self.output_file.set(output_file)


    def initialise_conf(self):
        self.conf = configparser.ConfigParser()
        self.conf.optionxform = lambda option: option.capitalize()
        self.conf.add_section('general')

    conf_types = [('ascii-phonons configuration', '.conf'),
                  ('all files', '.*')]

    def find_and_write_conf(self):
        output_file = filedialog.asksaveasfilename(defaultextension='.conf',
                                                   filetypes=self.conf_types)
        self.write_conf(output_file)
        self.message.set('Saved config to {0}'.format(output_file))

    def write_conf(self, filename):
        self.update_conf()
        with open(filename, 'w') as f:
            self.conf.write(f)

    def find_and_read_conf(self):
        input_file = filedialog.askopenfilename(defaultextension='.conf',
                                                filetypes=self.conf_types)
        self.read_conf(input_file)
        self.message.set('Loaded config from {0}'.format(input_file))

    def read_conf(self, filename):
        self.update_conf()
        self.conf.read(filename)
        self.update_view()

    def update_conf(self):
        for k, v in {
            'input_file': self.input_file,
            'mode_index': self.mode.get(),
            'camera_rot': self.camera_rot.get(),
            'end_frame': self.end_frame.get(),
            'gif': self.gif.get(),
            'miller': (self.miller_a.get(), self.miller_b.get(),
                       self.miller_c.get()),
            'montage': self.montage.get(),
            'n_frames': self.n_frames.get(),
            'normalise_vectors': self.normarrows.get(),
            'offset_box': (self.unitcell_X.get(), self.unitcell_Y.get(),
                           self.unitcell_Z.get()),
            'orthographic': self.orthographic.get(),
            'output_file': self.output_file.get(),
            'scale_arrow': self.arrowsize.get(),
            'scale_atom': self.atomsize.get(),
            'scale_vib': self.vibsize.get(),
            'show_box': self.show_box.get(),
            'start_frame': self.start_frame.get(),
            'supercell': (self.supercell_X.get(), self.supercell_Y.get(),
                          self.supercell_Z.get()),
            'vectors': self.arrows.get(),
            'zoom': self.zoom.get()
            }.items():
            if type(v) == str:
                self.conf.set('general', k, v)
            else:
                self.conf.set('general', k, dumps(v))

    def update_view(self):
        """Update the GUI values to reflect state of config object"""
        if self.conf.has_option('general', 'input_file'):
            self.input_file = self.conf.get('general', 'input_file')
        for k, setter, t in (
            ('mode_index', self.mode.set, int),
            ('camera_rot', self.camera_rot.set, float),
            ('end_frame', self.end_frame.set, int),
            ('gif', self.gif.set, bool),
            ('miller', (self.miller_a.set, self.miller_b.set,
                        self.miller_c.set), float),
            ('montage', self.montage.set, bool),
            ('n_frames', self.n_frames.set, int),
            ('normalise_vectors', self.normarrows, bool)
            ('offset_box', (self.unitcell_X.set, self.unitcell_Y.set,
                            self.unitcell_Z.set), float),
            ('output_file', self.output_file.set, str),
            ('orthographic', self.orthographic.set, bool),
            ('scale_arrow', self.arrowsize.set, float),
            ('scale_atom', self.atomsize.set, float),
            ('scale_vib', self.vibsize.set, float),
            ('show_box', self.show_box.set, bool),
            ('start_frame', self.start_frame.set, int),
            ('supercell', (self.supercell_X.set, self.supercell_Y.set,
                           self.supercell_Z.set), int),
            ('vectors', self.arrows.set, bool),
            ('zoom', self.zoom.set, float)):
            if self.conf.has_option('general', k):
                if type(setter) == tuple:
                    values = ascii_phonons.parse_tuple(self.conf.get('general', k),
                                                       value_type=t)
                    for s, value in zip(setter, values):
                        s(value)

                elif t == bool:
                    setter(self.conf.getboolean('general', k))

                else:
                    setter(t(self.conf.get('general', k)))

    def render(self):
        self.update_conf()
        self.conf.set('general', 'gui', 'False')
        self.write_conf(self.tmp_conf_file)
        if (not self.conf.has_option('general','input_file')
            or self.conf.get('general','input_file') == ''):
            self.message.set('Please open a .ascii input file')
        else:
            if (self.conf.has_option('general', 'montage') and
                self.conf.getboolean('general', 'montage')):
                if (self.conf.getint('general', 'start_frame')
                    == self.conf.getint('general', 'end_frame')):
                    ascii_phonons.montage_static(config=self.tmp_conf_file,
                                                 output_file=self.output_file.get())
                else:
                    ascii_phonons.montage_anim(config=self.tmp_conf_file,
                                               output_file=self.output_file.get())
            else:
                ascii_phonons.call_blender(config=self.tmp_conf_file,
                                           gui=False)
            self.message.set('Rendering complete')
        remove(self.tmp_conf_file)

    def preview(self):
        self.update_conf()
        self.write_conf(self.tmp_conf_file)
        if (not self.conf.has_option('general','input_file')
            or self.conf.get('general','input_file') == ''):
            self.message.set('Please open a .ascii input file')
        else:
            ascii_phonons.call_blender(config=self.tmp_conf_file,
                                       preview=self.preview_file,
                                       gif=False, static=True)
            remove(self.tmp_conf_file)

            # Pack the preview image into the right frame
            self.preview_pil = ImageTk.Image.open(self.preview_file + '.png')
            self.preview_tk = ImageTk.PhotoImage(self.preview_pil)
            self.preview_label.configure(image=self.preview_tk)

            remove(self.preview_file + '.png')

            self.message.set('Preview complete')

    def launch_blender(self):
        self.update_conf()
        self.write_conf(self.tmp_conf_file)
        ascii_phonons.call_blender(config=self.tmp_conf_file,
                                   output_file=False, gui=True)
        remove(self.tmp_conf_file)

if __name__ == "__main__":
    root = tk.Tk()
    root.wm_title("ascii-phonons")
    top = Application(root)
    root.mainloop()
